// 引入标准库头文件
#include <iostream>   // 标准输入输出流
#include <string>     // 字符串处理
#include <cstdlib>    // 通用工具函数，如 exit, EXIT_FAILURE
#include <vector>     // 动态数组 vector
#include <unistd.h>   // 包含 fork, pipe, dup2, read, write, close, getpid, exit
#include <ctime>      // time 函数，用于随机数种子
#include <sys/wait.h> // waitpid 函数，用于等待子进程
#include "task.hpp"   // 引入自己定义的任务头文件
#include <cstdint>    // 声明 uint32_t 等固定宽度整数类型
// #include <cstdint> // 重复包含

using namespace std; // 使用标准命名空间

// 定义错误码枚举，用于 main 函数返回不同的错误类型
enum
{
    UsageError = 1, // 使用方法错误 (命令行参数不正确)
    ArgError,       // 参数错误 (子进程数量不合法)
    PipeError,      // 管道创建错误
};

// 打印程序使用方法，指导用户如何运行程序
void Usage(const std::string &proc)
{
    cout << "Usage:" << proc << " subprocess-num" << endl; // proc 是程序名，subprocess-num 是用户期望的子进程数量
}

// Channel类：用于在父进程中管理与每个子进程的通信通道（管道的写端）
class Channel
{
public:
    // 构造函数：初始化通道信息
    Channel(int wfd, pid_t sub_id, const std::string &name)
        : _wfd(wfd), _sub_process_id(sub_id), _name(name)
    {
    }

    // 打印调试信息
    void PrintDebug()
    {
        cout << "_wfd:" << _wfd;                         // 通道的写端文件描述符
        cout << ",_sub_process_id: " << _sub_process_id; // 子进程的 PID
        cout << ",_name: " << _name << endl;             // 通道的名称
    }

    // 获取通道名称
    string name() { return _name; }

    // 获取写端文件描述符
    int wfd() { return _wfd; }

    // 获取子进程ID
    pid_t pid() { return _sub_process_id; }

    // 关闭写端（父进程调用，通知子进程退出）
    void Close() { close(_wfd); }

    ~Channel() {} // 析构函数
private:
    int _wfd;              // 管道的写端文件描述符
    pid_t _sub_process_id; // 关联的子进程的 ID
    std::string _name;     // 通道的逻辑名称
};

// ProcessPool类：进程池的核心实现
class ProcessPool
{
public:
    // 构造函数：初始化期望的子进程数量
    ProcessPool(int sub_process_num) : _sub_process_num(sub_process_num) {}

    // 创建子进程并建立通信管道
    // work 是一个函数指针，指向子进程执行的入口函数 (即 task.hpp 中的 worker 函数)
    int CreateProcess(work_t work)
    {
        std::vector<int> fds;               // 临时存储父进程创建的**所有**管道的写端，用于子进程关闭不需要的写端
        channels.reserve(_sub_process_num); // 预分配 vector 空间，提高效率
        // 实际上是调用了 channels 这个 std::vector 对象(channel类型的)的 reserve() 方法。

        for (int number = 0; number < _sub_process_num; number++) // 循环创建指定数量的子进程
        {
            int pipefd[2];        // pipefd[0] 是读端，pipefd[1] 是写端
            int n = pipe(pipefd); // 创建一个无名管道
            if (n < 0)            // 检查管道创建是否成功
            {
                perror("pipe failed"); // 打印错误信息
                // 在实际应用中，创建管道失败可能需要清理已创建的子进程
                return PipeError; // 返回管道创建错误码
            }

            pid_t id = fork(); // 创建子进程
            if (id < 0)        // 检查 fork 是否失败
            {
                perror("fork failed"); // 打印错误信息
                // fork 失败是严重错误，可能需要清理已创建的管道和子进程
                close(pipefd[0]); // 关闭当前创建的管道两端
                close(pipefd[1]);
                return ArgError; // 返回错误码 (这里用了 ArgError，也可以自定义 ForkError)
            }

            if (id == 0) // 子进程执行逻辑 (fork 返回 0 表示当前是子进程)
            {
                // 子进程继承了父进程在本次循环及之前循环创建的所有管道的文件描述符
                // 它只需要保留与父进程通信的那个管道的读端 pipefd[0]
                // 它应该关闭所有其他的管道文件描述符，特别是写端
                // 关闭本次创建的管道的写端
                close(pipefd[1]);

                // 关闭子进程继承的 所有父进程的写端 (在 fds 列表中记录的)
                // 这样做是为了确保当父进程退出或关闭其写端时，子进程能收到 EOF
                // 如果子进程不关闭这些写端，即使父进程关闭了，子进程自己还持有写端，管道引用计数不为 0，read 就不会返回 0
                if (!fds.empty())
                {
                    // std::cout << "close w fd in child " << getpid() << ": "; // 调试输出
                    for (auto fd_to_close : fds)
                    {
                        close(fd_to_close);
                        //  std::cout << fd_to_close << " "; // 调试输出
                    }
                    // std::cout << std::endl; // 调试输出
                }

                // 重定向子进程的标准输入 (文件描述符 0) 到管道的读端 pipefd[0]
                // 这样，子进程从标准输入读取数据，实际上就是从管道中读取父进程写入的数据
                if (dup2(pipefd[0], 0) == -1)
                {
                    perror("dup2 failed in child");
                    exit(EXIT_FAILURE); // 重定向失败是严重错误，子进程退出
                }
                close(pipefd[0]); // 重定向后，原始的 pipefd[0] 文件描述符就不再需要了，可以关闭

                // 执行工作函数 (task.hpp 中的 worker 函数)
                // worker 函数会从标准输入 (现在是管道的读端) 读取命令
                work(pipefd[0]); // 注意：这里传递的 pipefd[0] 在 dup2 后其实已经关闭了，worker 函数内部使用 0 (stdin)
                                 // 所以这里的参数 fd 传给 worker 似乎多余且无效，worker 函数内部是 hardcode read(0, ...)

                exit(0); // 子进程执行完工作函数后正常退出
            }

            // 父进程继续执行 (fork 返回大于 0 的值表示当前是父进程，返回值是子进程的 PID)
            pid_t sub_id = id;                             // 获取子进程 ID
            string cname = "channel-" + to_string(number); // 为这个子进程创建一个名称

            // 父进程关闭本次创建的管道的读端
            close(pipefd[0]);

            // 保存通道信息（管道的写端文件描述符，子进程ID，名称）
            channels.push_back(Channel(pipefd[1], sub_id, cname));
            // 将本次创建的管道写端添加到临时 fds 列表中
            fds.push_back(pipefd[1]);
        }
        // 父进程创建完所有子进程后，fds 列表的作用就完成了
        return 0; // 返回成功
    }

    // 轮询方式获取下一个可用的通道索引
    // 用于父进程在多个子进程之间轮流分发任务
    int NextChannel()
    {
        static int next = 0;     // 静态变量，记录下一次分配的索引
        int c = next;            // 获取当前索引
        next++;                  // 索引递增
        next %= channels.size(); // 模以通道数量，实现循环轮询
        return c;                // 返回通道索引
    }

    // 向指定索引的通道（子进程）发送任务码
    void SendTaskCode(int index, uint32_t code)
    {
        // 打印调试信息，说明发送了哪个任务码给哪个子进程
        cout << "send code: " << code << " to " << channels[index].name() << " sub process id: " << channels[index].pid() << endl;

        // 向指定通道（子进程）对应的管道写端写入任务码
        write(channels[index].wfd(), &code, sizeof(code));
    }

    // 终止所有子进程并回收资源
    void KillAll()
    {
        // 遍历所有通道
        for (auto &channel : channels)
        {
            // 关闭管道的写端
            // 父进程关闭管道的写端会使得子进程在该管道的读端 read() 调用返回 0 (EOF)
            // 这将触发子进程的 worker 函数退出循环
            channel.Close();
            std::cout << channel.name() << " close done, sub process quit now: " << channel.pid() << std::endl;
        }

        // 等待所有子进程退出，回收资源
        // 遍历所有通道，根据保存的子进程 PID 调用 waitpid
        for (auto &channel : channels)
        {
            pid_t pid = channel.pid(); // 获取子进程 PID
            // waitpid 阻塞等待指定 PID 的子进程退出
            pid_t rid = waitpid(pid, nullptr, 0);
            if (rid == pid) // 检查是否成功等待到该子进程
            {
                std::cout << "wait sub process" << pid << "sucess.." << std::endl;
            }
            else
            {
                perror("waitpid failed"); // 如果等待失败
            }
        }
        std::cout << "All sub processes waited sucess.." << std::endl;
    }

    // 等待子进程（预留接口） - 这里的 KillAll 已经包含了等待，所以这个函数可能暂时不用
    void Wait()
    {
        // 可以实现其他等待子进程的逻辑，或者根据 KillAll 的实现，这里可能不需要额外操作
    }

    // 打印通道调试信息
    void Debug()
    {
        cout << "Debug Channels:" << endl;
        for (auto &channel : channels)
        {
            channel.PrintDebug();
        }
        cout << "Debug Channels done." << endl;
    }

    ~ProcessPool()
    {
        // 析构函数，确保资源被释放（如果 KillAll 没有被调用）
        // 通常应该确保 KillAll 或其他清理逻辑在 delete ProcessPool 之前执行
    }

private:
    int _sub_process_num;     // 进程池中的子进程数量
    vector<Channel> channels; // 存储每个子进程对应的通信通道（管道写端和子进程信息）
};

// 控制进程池执行指定数量的任务
void CtrlProcessPool(ProcessPool *processpool_ptr, int cnt)
{
    std::cout << "Start controlling process pool for " << cnt << " tasks." << std::endl;
    while (cnt) // 循环执行指定数量的任务
    {
        int channel_index = processpool_ptr->NextChannel(); // 获取下一个子进程的索引（轮询）
        uint32_t code = NextTask();                         // 获取一个随机的任务码
        processpool_ptr->SendTaskCode(channel_index, code); // 将任务码发送给选定的子进程
        sleep(1);                                           // 等待1秒，控制任务发送速度
        cnt--;                                              // 任务计数减一
    }
    std::cout << "Finished controlling process pool." << std::endl;
}

// 主函数：程序的入口点
// 正确运行方式是：./processpool 5，命令行参数就是2
int main(int argc, char *argv[])
{
    // argc 和 argv 是由操作系统自动处理并传递给程序的。
    // argc 是命令行参数的数量，argv 是一个字符串数组，包含所有参数
    // argv[0] 是程序名，argv[1] 是第一个参数（子进程数量）

    //  检查命令行参数数量
    if (argc != 2) // 期望参数：程序名 + 子进程数量
    {
        Usage(argv[0]);    // 打印使用方法
        return UsageError; // 返回使用方法错误码
    }

    // 获取命令行参数指定的子进程数量
    int sub_process_num = std::stoi(argv[1]);
    // 检查子进程数量是否合法
    if (sub_process_num <= 0)
    {
        fprintf(stderr, "Error: subprocess-num must be a positive integer.\n");
        Usage(argv[0]);
        return ArgError; // 返回参数错误码
    }

    // 初始化随机数生成器种子
    // 使用当前时间作为种子，保证每次运行程序生成的随机数序列不同
    srand(static_cast<uint64_t>(time(nullptr)));

    // 创建进程池对象并启动子进程
    // 在堆上动态分配 ProcessPool 对象
    ProcessPool *processpool_ptr = new ProcessPool(sub_process_num);

    // 调用 CreateProcess 函数，创建子进程和管道
    // 将 worker 函数的地址作为参数传递，子进程会执行 worker 函数
    int create_ret = processpool_ptr->CreateProcess(worker);
    if (create_ret != 0)
    {
        fprintf(stderr, "Failed to create process pool, error code: %d\n", create_ret);
        delete processpool_ptr; // 清理已分配资源
        return create_ret;      // 返回创建错误码
    }

    // 父进程执行的任务控制逻辑
    // 如果 CreateProcess 返回 0，表示当前是父进程在继续执行
    // 如果 CreateProcess 在子进程中调用 exit()，则子进程不执行到这里

    // 打印调试信息（显示父进程持有的管道写端和子进程ID）
    processpool_ptr->Debug();

    // 控制进程池执行指定数量的任务 (这里执行 10 个任务)
    CtrlProcessPool(processpool_ptr, 10);
    std::cout << "task run done" << std::endl;

    // 终止所有子进程并回收资源
    processpool_ptr->KillAll();

    // 等待所有子进程结束 (在 KillAll 中已经包含了 waitpid)
    // processpool_ptr->Wait();

    // 释放进程池对象占用的内存
    delete processpool_ptr;

    return 0; // 程序正常退出
}